#! /usr/bin/env python
# -*- coding: UTF-8 -*-
#
# Copyright (c) 2008 jiakuan.net and widenhome.com, All rights reserved.
# 
# Created on 2008-12-2, by Jiakuan Wang.
#

"""This script uses pygettext.py to generate all messages to PO files. It 
could be used to regenerate PO files at any time while developing application.

The following describes what this script actually do step by step:

 1. Check if there original generated po files, if there are po files generated
    before by the script, they should be separated by the language tag in the 
    file names, that is, every language has one po file for the application. 
    Now read the po files and save the old messages of the po files into 
    several python dictionaries according to different languages. These 
    dictionaries will be used in the fourth step of this script. 

 2. Since there might be a length limitation on system command on some 
    operating systems, so if there are many python files in the application, 
    the pygettext.py script, which extracts messages from python files, will be
    failed when we execute it because the command line is too long.
    
    To solve this problem, we can merge all the python files of the application 
    into one temporary file, and then use the pygettext.py script to extract
    messages from this temporary file, and finally delete the temporary file.
    
    So in this step we just walk through the directory tree from the base 
    directory of the application, and read every python file in every directory 
    and subdirectory in the directory tree and append the content of every 
    python file to the global temporary file.

 3. From the temporary python file generated in the previous step, we extract 
    messages into po files by pygettext.py script according to different 
    languages. After that, delete the temporary file.

 4. Check the message dictionaries generated in the first step to find out if 
    there are translated messages for the messages in the new PO files, and 
    merge the translated messages into the new PO files according to different
    languages.

To use this script, just copy this file to the base directory of the application.
"""

import os
import time

# The header of the PO file, it will be used to generate PO files.
po_header = """# PO File Generated by py2po.py script.
# Copyright (C) %s Widen, All rights reserved.
# Jiakuan Wang <delight.wjk@gmail.com>, %s.
#
""" % (time.strftime('%Y'), time.strftime('%Y-%m-%d'))

# Customize the parameters for specific project.
default_parameters = {"Project.Id.Version":"Unknown",
                      "Creation.Date":time.strftime('%Y-%m-%d'),
                      "Last.Translator":"Widen",
                      "Last.Translator.Email":"delight.wjk@gmail.com",
                      "Team.Name":"Widen",
                      "Team.Email":"delight.wjk@gmail.com"}

# Excluded python files which will not be merged to the temporary file.
excluded_files = ['_temp.py', 'i18n.py', 'py2po.py', 'po2mo.py', 'msgfmt.py', 
                  'pygettext.py']
        
class Executant:
    default_message = """\"\"
\"Project-Id-Version: ${Project.Id.Version}\\n\"
\"POT-Creation-Date: ${Creation.Date}\\n\"
\"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\\n\"
\"Last-Translator: ${Last.Translator} <${Last.Translator.Email}>\\n\"
\"Language-Team: ${Team.Name} <${Team.Email}>\\n\"
\"MIME-Version: 1.0\\n\"
\"Content-Type: text/plain; charset=utf-8\\n\"
\"Content-Transfer-Encoding: 8bit\\n\"
\"Generated-By: pygettext.py 1.5\\n\"
"""

    ##
    # The constructor of the py2po.Executant class, the method mainly receives 
    # customized parameters when it is being initialized.
    #
    # @param base_dir: The base directory of a python project.
    # @param domain: The domain of the i18n, that is, the file name of the PO files.
    # @param localedir: The locale directory for the python application.
    # @param languages: The supported languages for the specific application.
    # @param parameters: The user can pass in the customized parameters.
    # 
    def __init__(self, base_dir, domain, localedir, languages, parameters=None):

        # The base directory which contains the application files that need to be 
        # internationalized, default it is the same directory of this script.
        self.base_dir = base_dir
        
        # The domain of this i18n application.
        self.domain = domain
        
        # The locale directory
        self.localedir = localedir
        
        # Supported languages
        self.languages = languages
        
        self.parameters = parameters

        # Temporary python file
        self.temp_py_file = os.path.join(base_dir, '_temp.py')
        
         # Old message dictionary, include all languages, follow this format: 
        # msgstr = old_msg_dict[language][msgid]
        self.old_msg_dict = {}
        for language in languages:
            self.old_msg_dict[language] = {'':''}

    def init_parameters(self):
        # Initialize the empty message with specified parameters.
        if self.parameters:
            for key in default_parameters.keys():
                if key in self.parameters.keys():
                    default_parameters[key] = self.parameters[key]

        for key in default_parameters.keys():
            print key, default_parameters[key]
            self.default_message = self.default_message.replace("${"+key+"}", default_parameters[key])
        
    def get_po_filename(self,language):
        "Get the PO file name by specified language."
        
        folder = "".join(['locale/', language, '/LC_MESSAGES/']);
        
        if not os.path.exists(folder):
            os.makedirs(folder)
            print "Create folder:", folder
        
        return "".join([folder, self.domain, '.po'])

    def build_msg_dict(self,language):
        '''Build message dictionary from PO file. The following is the example of 
        a PO file. 
            
        -----------------------------------------------------------------
        # PO File Generated by py2po.py script.
        # Copyright (C) 2008 Widen
        # Jiakuan Wang <delight.wjk@gmail.com>, 2008.
        #
        msgid ""
        msgstr ""
        "Project-Id-Version: PACKAGE VERSION\n"
        "POT-Creation-Date: 2008-08-07 00:15\n"
        "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
        "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
        "Language-Team: LANGUAGE <LL@li.org>\n"
        "MIME-Version: 1.0\n"
        "Content-Type: text/plain; charset=utf-8\n"
        "Content-Transfer-Encoding: ENCODING\n"
        "Generated-By: pygettext.py 1.5\n"
        
        
        #: source.py:19
        msgid "Javava Task"
        msgstr ""
        
        #: source.py:39
        msgid "&File"
        msgstr ""
        
        #: source.py:41
        msgid "&Options..."
        msgstr ""
        
        #: source.py:73
        msgid ""
        "Choose a preferred language, it will be valid after \n"
        "restarting application."
        msgstr ""
        -----------------------------------------------------------------
        
        Regarding the PO format above, either the value of the 'msgid' or the 
        'msgstr' may be multiple line value. This method parses this type of file
        in to msgid & msgstr pairs, and return them as a python dictionary.
        '''
        
        MSG_ID = 'msgid'
        MSG_STR = 'msgstr'
    
        # This variable will store the msgid and msgstr pairs and to be returned.
        msg_dict = {}
        
        # Store the value of each msgid temporarily.
        msgid = ''
        
        # Store the value of each msgstr temporarily.
        msgstr = ''
        
        # Record the current processing content for the processor, this variable
        # will let the loop know what the content type of the current line. The 
        # values of this variable should be 'msgid' or 'msgstr'.
        curr_processing = ''
        
        po_file = open(self.get_po_filename(language))
        
        # Deal with each line of the PO file
        for line in po_file:
            if line:            
                # If the line is not empty, its prefix may be one of four types:
                #   1. Start with '#' - it means that is a comment line, skip it.
                #   2. Start with 'msgid' - it means we begin to process a msgid.
                #   3. Start with 'msgstr' - it means we begin to process a msgstr.
                #   4. Start with '"' - it means this line is pure content for 
                #      msgid or msgstr, it is subject to the curr_processing 
                #      declared above.
                if line.startswith('#'):
                    continue
    
                elif line.startswith(MSG_ID):
                    # Check if the msgid is empty or not, if it is not empty, put 
                    # the values msgid and msgstr into dictionary.
                    if msgid:
                        msg_dict[msgid] = msgstr
                    
                    # Ready to process new msgid.
                    curr_processing = MSG_ID
                    msgid = line[len(MSG_ID)+1:]
    
                elif line.startswith(MSG_STR):
                    curr_processing = MSG_STR
                    msgstr = line[len(MSG_STR)+1:]
    
                elif line.startswith('"'):
                    # This line is pure content, if the currProcess is msgid, then
                    # append this line to the content of msgid, otherwise append it 
                    # to the content of msgstr.
                    if curr_processing == MSG_ID:
                        msgid += line
                    elif curr_processing == MSG_STR:
                        msgstr += line
    
        # Deal the last msgid & msgstr pair of the PO file
        if msgid:
            msg_dict[msgid] = msgstr
        
        # After visited all lines in the PO file, close it.
        po_file.close()
        
        # Return the dictionary.
        return msg_dict
    
    def check_old_po_files(self):
        "Check the old PO files generated before."
    
        for language in self.languages:
            po_file = self.get_po_filename(language)
            if os.path.exists(po_file):
                print 'Find old file:', os.path.abspath(po_file)
                self.old_msg_dict[language] = self.build_msg_dict(language)
    
    def merge_py_files(self):
        "Merage all python files to one temporary file."
        
        # Initialize the temporary file.
        temp_file = open(self.temp_py_file, 'w')
        temp_file.write('')
        temp_file.close()
        
        # Open temporary python file as append mode.
        temp_file = open(self.temp_py_file, 'a')
        os.path.walk(self.base_dir, self.py_file_visitor, temp_file)
        temp_file.close()
    
    def py_file_visitor(self, temp_file, dir_name, files_here):
        "Append the content of every python file to the temporary python file."
        
        for file_name in files_here:
            if file_name.endswith('.py'):
                # Don't merge the excluded py files.
                if file_name in excluded_files:
                    continue
                
                path_name = os.path.join(dir_name, file_name)
                print 'Merge the content of file:', path_name
                py_file = open(path_name)
                temp_file.write(py_file.read())
                py_file.close()
    
    def gen_po_files(self):
        '''Generate the new PO files from the temporary files by different 
        language.
        '''
        for language in self.languages:
            po_filename = self.get_po_filename(language)
            import pygettext
            cmd = 'python ' + os.path.abspath(pygettext.__file__) + ' -o ' + po_filename + ' ' + self.temp_py_file
            os.system(cmd)
            print 'Execute command:', cmd
            
            # Change the default empty message string.
            msg_dict = self.build_msg_dict(language)
            msg_dict['""\n'] = self.default_message
            self.save_msg_dict_to_file(msg_dict, po_filename)

        # After all new PO files are generated, delete the python temporary file.
        os.remove(self.temp_py_file)
    
    def merge_old_msgs(self):
        '''This method checks the old message dictionary to see if there are some 
        exist translated messages for the new messages of the new PO file. First, 
        we will build new message dictionary from the new PO file, and update the 
        new message dictionary through the old message dictionary. Finally, write 
        the new message dictionary to the PO file.
        '''
       
        for language in self.languages:
            # Build new message dictionary from the new PO file.
            new_msg_dict = self.build_msg_dict(language)
            
            # Find if there is a new key in the message dictionary, and set the 
            # value of the key in new message dictionary by the value of the key
            # in the old message dictionary.
            for new_key in new_msg_dict.keys():
                temp_dict = self.old_msg_dict[language]
                if len(temp_dict) > 0 and new_key in temp_dict.keys():
                    print 'Find the key in old message dictionary:', new_key.strip()
                    new_msg_dict[new_key] = self.old_msg_dict[language][new_key]
            
            # Save the new message dictionary to PO file.
            po_filename = self.get_po_filename(language)
            print 'Save merged file:', os.path.abspath(po_filename)
            self.save_msg_dict_to_file(new_msg_dict, po_filename)
        
    def save_msg_dict_to_file(self, msg_dict, po_filename):
        '''Save the message dictionary to the specified file following the correct
        format.
        '''
        
        # Open the file
        po_file = open(po_filename, 'w')
        
        # Declare a temporary variable to store the content of the file.
        content = po_header
        keys = msg_dict.keys()
        
        # Sort the keys
        keys.sort()
        
        # Deal with keys and values and merge them in content variable.
        for key in keys:
            content = content + 'msgid ' + key
            content = content + 'msgstr ' + msg_dict[key] + '\n'
        
        # Write content to file.
        po_file.write(content)
        po_file.close()

    def execute(self):
        print '\n', '-' * 60, '\nScript py2po.py starts to run...\n', '-' * 60
        
        # Initialize parameters
        print '\n', '-' * 60, '\nInitialize parameters...\n', '-' * 60
        self.init_parameters()
        
        # Check the old PO files.
        print '\n', '-' * 60, '\nCheck the old PO files...\n', '-' * 60
        self.check_old_po_files()
        
        # Merge all python files of the application.
        print '\n', '-' * 60, '\nMerge all PY files to one temporary file...\n', '-' * 60
        self.merge_py_files()
        
        # Generate new PO files.
        print '\n', '-' * 60, '\nGenerate new PO files...\n', '-' * 60
        self.gen_po_files()
        
        # Merge old messages from the old message dictionaries.
        print '\n', '-' * 60, '\nMerge old messages to the new PO files...\n', '-' * 60
        self.merge_old_msgs()

        print '\n', '-' * 60, '\nScript py2po.py is executed successfully...\n', '-' * 60
        
if __name__ == '__main__':
    print os.path.dirname(__file__)
    